package cn.boodqian.airreport;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;

import cn.boodqian.utils.*;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.animation.AnimationUtils;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.SimpleExpandableListAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;

import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;

public class AirreportProfileActivity extends SherlockActivity implements  OnGroupClickListener, OnItemSelectedListener  {
	private ArrayList<HashMap<String, String>> mProfiles = new ArrayList<HashMap<String,String>>();
	private static Gson mGson = new GsonBuilder().setDateFormat("yyyy/MM/dd HH:mm:ss Z").create();
	private ServerMessageHandler handler = new ServerMessageHandler(this);
	private ExpandableListView mListProfile;
	private HistoryView mHistoryView;
	private TextView mTextHistoryTitle;
	private Spinner mSpinnerHistoryTime;
	private ColorAdapter mProfileAdapter = null;
	@SuppressWarnings("deprecation")
    private GestureDetector mGestureDetector = new GestureDetector(new MyGestureDetectorListener());
	private GAERequest mGAERequest;
	private GAERequest mUpdateRequest;
	private Handler mGAEHandler;
	
	private boolean mRestoringPrefs = false;
	private boolean ignoreNextClick = false;
	private String mHistoryLocId;
	private String mHistoryLocName;
	
	private final int ADD_PROFILE_REQUEST_CODE=0;
	private ArrayList<ArrayList<HashMap<String, String>>> mDetailDataList = new ArrayList<ArrayList<HashMap<String,String>>>();
	private ViewFlipper mFlipper;
	
	class MyGestureDetectorListener extends SimpleOnGestureListener {
		private static final int FLING_MIN_DISTANCE = 80;
		private static final int FLING_THRESHOLD_VELOCITY = 200;

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			float scale = getResources().getDisplayMetrics().densityDpi;
			if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE*scale/160f
					&& Math.abs(velocityX) > FLING_THRESHOLD_VELOCITY*scale/160f) {
				if(!AirreportProfileActivity.this.onFling(e1, e2)) return false;
				
				mFlipper.setInAnimation(AnimationUtils.loadAnimation(
						AirreportProfileActivity.this, R.anim.slide_right_in));
				mFlipper.setOutAnimation(AnimationUtils.loadAnimation(
						AirreportProfileActivity.this, R.anim.slide_left_out));
				mFlipper.showNext();
				return true;
			} else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE*scale/160f
					&& Math.abs(velocityX) > FLING_THRESHOLD_VELOCITY*scale/160f) {
				if(!AirreportProfileActivity.this.onFling(e1, e2)) return false;
				
				mFlipper.setInAnimation(AnimationUtils.loadAnimation(
						AirreportProfileActivity.this, R.anim.slide_left_in));
				mFlipper.setOutAnimation(AnimationUtils.loadAnimation(
						AirreportProfileActivity.this, R.anim.slide_right_out));
				mFlipper.showPrevious();
				return true;
			}
			return false;
		}
	}
	
	private class ColorAdapter extends SimpleExpandableListAdapter {

		public ColorAdapter(Context context,
				List<? extends Map<String, ?>> groupData, int groupLayout,
				String[] groupFrom, int[] groupTo,
				List<? extends List<? extends Map<String, ?>>> childData,
				int childLayout, String[] childFrom, int[] childTo) {
			super(context, groupData, groupLayout, groupFrom, groupTo, childData,
					childLayout, childFrom, childTo);
		}

		@Override
		public View getGroupView(int position, boolean isExpanded, View convertView, ViewGroup parent) {
			View view = super.getGroupView(position, isExpanded, convertView, parent);
			Map<String, String> map = (Map<String, String>) this.getGroup(position);
			
			TextView aqiView = (TextView)view.findViewById(R.id.text_aqi);
			TextView aqidescView = (TextView)view.findViewById(R.id.text_aqi_desc);
			
			if( map.get("id").length() == 0 ) {
				return view;
			}
			
			Integer aqi = 0;
			Float cc = 0f;
			try {
				aqi = Integer.parseInt(map.get("aqi_value"));
				cc = Float.parseFloat(map.get("cc_value"));
			} catch(NumberFormatException e) {
				// do nothing
			}
			if( cc.compareTo(0f) == 0 ) {
				aqiView.setTextColor( Color.GRAY );
			} else {
				aqiView.setTextColor( getResources().getColor(AQI.AQIColor(aqi)) );
				aqidescView.setTextColor( getResources().getColor(AQI.AQIColor(aqi)) );
			}

			return view;
		}

		@Override
		public View getChildView(int groupPosition, int childPosition,
				boolean isLastChild, View convertView, ViewGroup parent) {
			View view =  super.getChildView(groupPosition, childPosition, isLastChild,
					convertView, parent);
			
			TextView nowView = (TextView)view.findViewById(R.id.text_realtime_value);
			TextView avgView = (TextView)view.findViewById(R.id.text_avg24_value);
			
			Map<String, String> map = (Map<String, String>) this.getChild(groupPosition, childPosition);
			
			Integer aqi_now = 0;
			Float cc_now = 0f;
			Integer aqi_avg = 0;
			Float cc_avg = 0f;
			
			try {
				aqi_now = Integer.parseInt(map.get("aqi_now"));
				cc_now = Float.parseFloat(map.get("cc_now"));
				aqi_avg = Integer.parseInt(map.get("aqi_avg"));
				cc_avg = Float.parseFloat(map.get("cc_avg"));
			} catch(NumberFormatException e) {
				// do nothing
			}
			
			if( cc_now.compareTo(0f) == 0 ) {
				nowView.setTextColor( Color.GRAY );
			} else {
				nowView.setTextColor( getResources().getColor(AQI.AQIColor(aqi_now)) );
			}
			
			if( cc_avg.compareTo(0f) == 0 ) {
				avgView.setTextColor( Color.GRAY );
			} else {
				avgView.setTextColor( getResources().getColor(AQI.AQIColor(aqi_avg)) );
			}
			
			return view;
		}
	}

	private class GAEHandler extends Handler {
		private CharSequence originalTitle = "";
		@Override
		public void handleMessage(Message msg) {
			switch(msg.what) {
			case GAERequest.MESSAGE_DNS:
				setTitle(R.string.gae_message_dns);
				break;
			case GAERequest.MESSAGE_QUERY:
				setTitle(
						String.format( getString(R.string.gae_message_query), msg.arg1, msg.arg2));
				break;
			case GAERequest.MESSAGE_DONE_OK:
				newAirdataArrived((String)msg.obj);
				setTitle(originalTitle);
				Toast.makeText(
						AirreportProfileActivity.this,
						getString( R.string.refresh_done ),
						Toast.LENGTH_LONG).show();
				break;
			case GAERequest.MESSAGE_DONE_ERR:
				setTitle(originalTitle);
				Toast.makeText(
						AirreportProfileActivity.this,
						getString(R.string.refresh_error) + "\n"
								+ mGAERequest.getLastError(),
								Toast.LENGTH_LONG).show();
				break;
			case GAERequest.MESSAGE_RUNNING:
				Toast.makeText(
						AirreportProfileActivity.this,
						getString(R.string.refresh_running),
						Toast.LENGTH_LONG).show();
				break;
			case GAERequest.MESSAGE_START:
				originalTitle = getTitle();
				Toast.makeText(
						AirreportProfileActivity.this,
						getString(R.string.refresh_submit),
						Toast.LENGTH_LONG).show();
				break;
			}
		}
	}
		
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.flipper);
		
		mListProfile = (ExpandableListView) findViewById(R.id.list_profile);
		mListProfile.setOnGroupClickListener(this);
		
		mTextHistoryTitle = (TextView) findViewById(R.id.history_view_title);
		mHistoryView = (HistoryView) findViewById(R.id.history_view);
		mFlipper = (ViewFlipper) findViewById(R.id.flipper);
		mSpinnerHistoryTime = (Spinner) findViewById(R.id.history_time_spinner);
		
		ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
		        R.array.history_time_array, android.R.layout.simple_spinner_item);
		// Specify the layout to use when the list of choices appears
		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		mSpinnerHistoryTime.setAdapter(adapter);
		mSpinnerHistoryTime.setOnItemSelectedListener(this);
		
		registerForContextMenu(mListProfile);
		
		mGAEHandler = new GAEHandler();
		mGAERequest = new GAERequest(GlobalData.gGAEProject, mGAEHandler);
		mUpdateRequest = new GAERequest(GlobalData.gUpdateHost, handler);
		mUpdateRequest.setSkipNormal( false );
		
		final ProgressDialog dlg = ProgressDialog.show(this,
				getString(R.string.refresh_wait),
				getString(R.string.restore_data), true);
		
		mRestoringPrefs = true;
		Thread t = new Thread() {
			public void run() {
				GlobalData.restorePrefs( AirreportProfileActivity.this );
				handler.post(new Runnable() {
					public void run() {
					    mRestoringPrefs = false;
					    checkUpdate();
					    try {
					        dlg.dismiss();
					    } catch( Exception e ) {
					        // In case activity finished before dismiss (several hits seen in crash report)
					        Log.w( "Dismiss exception: " + e.getLocalizedMessage() );
					        return;
					    }
                        if( GlobalData.gIsFirstRun ) {
                            showWhatsNew();
                        }
                        updateProfileListUI();
						updateAirdata(GlobalData.gProfileList.toArray(new String[]{}), new Runnable(){
							public void run() {
								updateProfileListUI();
							}
						});
					};
				});
			}
		};
		t.start();
		
		// Show tip in title bar
		//setTitle(getString(R.string.app_name) + " - " + getString(R.string.tip_help));
	}

	@Override
	protected void onStop() {
		super.onStop();
		
		GlobalData.savePrefs( this );
	}

	private void updateProfileListUI()
	{
		if(mProfiles == null) {
			Log.w("mProfiles  is null");
			return;
		}
		
		SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm", Locale.US);
		
		mProfiles.clear();
		mDetailDataList.clear();
		
		String standard = GlobalData.getPref_AQIStandard(this);
		Set<String> pollutantList = GlobalData.getPref_SelectPollutants(this);
		for(String id : GlobalData.gProfileList) {
			HashMap<String, String> location = GlobalData.getLocationInfo(id);
			if(location == null) continue;
			if( id.compareTo(location.get("id")) == 0) {
				// For group items (profile data)
				Object[] max_pollutant = GlobalData.getMaxPollutant(id, standard);
				String max_name = (String)max_pollutant[0];
				Integer aqi_value = (Integer)max_pollutant[1];
				Float cc_value = (Float)max_pollutant[2];
				HashMap<String, String> profile_item_data = new HashMap<String, String>();
				profile_item_data.putAll(location);
				profile_item_data.put("aqi_value", aqi_value.toString());
				profile_item_data.put("cc_value", cc_value.toString());
				AirData airdata =  GlobalData.getAirdata(id, "hour");
				Date lastupdate = null;
				if( airdata!=null) lastupdate = airdata.getTime();
				profile_item_data.put("time",
						lastupdate!=null ? fmt.format(lastupdate) : ""
						);
				profile_item_data.put("aqi_text",
						( cc_value.compareTo(0f)==0 ) ?
								getString(R.string.not_available) :
									String.format("%.1fµg/%d/%s", cc_value*1000, aqi_value, GlobalData.getPollutantDisplayName(max_name))
						);
				Log.i(id+","+aqi_value+","+AQI.AQICategory(aqi_value));
				profile_item_data.put("aqi_desc",
						( cc_value.compareTo(0f)==0 ) ? "" : getString(AQI.AQICategory(aqi_value))
						);
				mProfiles.add(profile_item_data);
				
				// For child items (detail data)
				ArrayList<HashMap<String,String>> childDataList = new ArrayList<HashMap<String,String>>();
				if( airdata != null ) {
					for(String name : GlobalData.gItemNameList) {
					    if( ! pollutantList.contains(name) ) continue;
						HashMap<String,String> map1 = new HashMap<String, String>();
						List<Float>data = airdata.getData(name);
						if( data != null && data.size()>0) {
							Float cc_now = data.get(data.size()-1);
							Integer aqi_now = AQI.getAQI(name, cc_now, standard);
							Float cc_avg = GlobalData.calcAverage(id, name);
							Integer aqi_avg = AQI.getAQI(name, cc_avg, standard);

							map1.put("cc_now", cc_now.toString());
							map1.put("aqi_now", aqi_now.toString());
							map1.put("cc_avg", cc_now.toString());
							map1.put("aqi_avg", aqi_avg.toString());

							map1.put("name", GlobalData.getPollutantDisplayName(name));
							map1.put("realtime",
									(cc_now.compareTo(0f)==0) ?
											getString(R.string.not_available)
											: String.format("%.1f/%03d:%s", cc_now*1000, aqi_now, getString(AQI.AQICategory(aqi_now))));
							map1.put("avg24",
									(cc_avg.compareTo(0f)==0) ?
											getString(R.string.not_available)
											: String.format("%.1f/%03d:%s", cc_avg*1000, aqi_avg, getString(AQI.AQICategory(aqi_avg))));
							childDataList.add(map1);
						}
					}
				}
				
				mDetailDataList.add(childDataList);
			}
		}

		HashMap<String, String> map = new HashMap<String, String>();
		map.put("id", "");
		map.put("city", "");
		map.put("name",  getString(R.string.profile_hint));
		map.put("aqi_text", "");
		map.put("aqi_desc", "");
		mProfiles.add(map);
		
		mDetailDataList.add(new ArrayList<HashMap<String,String>>());

		mProfileAdapter = new ColorAdapter(this,
				mProfiles, R.layout.profile_item,
				new String[]{"city", "name", "time", "aqi_text", "aqi_desc"},
				new int[]{R.id.text_city, R.id.text_name, R.id.text_time, R.id.text_aqi, R.id.text_aqi_desc},
				mDetailDataList, R.layout.airdata_detail_item,
				new String[]{"name", "realtime", "avg24"},
				new int[]{R.id.text_dataitem, R.id.text_realtime_value, R.id.text_avg24_value});
		mListProfile.setAdapter(mProfileAdapter);
	}
	
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		
		
		switch(requestCode) {
		case ADD_PROFILE_REQUEST_CODE:
			String newId = null;
			// data will be null if "back" pressed without selection
			if(  data != null && (newId = data.getStringExtra("id")) != null ) {
				GlobalData.gProfileList.add(newId);
				updateProfileListUI(); // To show to the user the station is added (will show N/A) 
				updateAirdata(new String[] {newId}, new Runnable(){
					public void run() {
						updateProfileListUI(); // Will update again with the data here
					}
				});
			}
			break;
		}
	}

	@Override
	public boolean onGroupClick(ExpandableListView parent, View v,
			int groupPosition, long id) {
		if( ignoreNextClick ) {
			ignoreNextClick = false;
			return true;
		}
		HashMap<String,String> map = (HashMap<String, String>)parent.getItemAtPosition(groupPosition);
		switch(parent.getId()) {
		case R.id.list_profile:
			// Seems map[id] will be null sometimes, not sure why yet
			if( map.get("id") != null && map.get("id").length()==0) {
				// Add a new profile
				Intent intent = new Intent(getApplicationContext(),
						LocationSelectActivity.class);
				startActivityForResult(intent, ADD_PROFILE_REQUEST_CODE);
				return true;
			}
			break;
		default:
			break;
		}
		return false;
	}

	@Override
	public void onCreateContextMenu(ContextMenu menu, View v,
			ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, v, menuInfo);
		
		ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
		int type  = ExpandableListView.getPackedPositionType(info.packedPosition);
		@SuppressWarnings("unused")
        int group = ExpandableListView.getPackedPositionGroup(info.packedPosition);
		@SuppressWarnings("unused")
        int child = ExpandableListView.getPackedPositionChild(info.packedPosition);
		
		if( type == ExpandableListView.PACKED_POSITION_TYPE_GROUP ) {
			android.view.MenuInflater inflater = getMenuInflater();
			inflater.inflate(R.menu.data_context, menu);
		}
	}

	@Override
	public boolean onContextItemSelected(android.view.MenuItem item) {
		ExpandableListContextMenuInfo info = 
				(ExpandableListContextMenuInfo) item.getMenuInfo();

		int groupPos = 0;
		@SuppressWarnings("unused")
        int childPos = 0;

		int type = ExpandableListView.getPackedPositionType(info.packedPosition);
		if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) 
		{
			groupPos = ExpandableListView.getPackedPositionGroup(info.packedPosition);
			childPos = ExpandableListView.getPackedPositionChild(info.packedPosition);
		}
		
		HashMap<String, String> map = mProfiles.get(groupPos);
		String locId = map.get("id");

		switch (item.getItemId()) 
		{
		case R.id.menu_remove:
			// Remove all
			while( GlobalData.gProfileList.remove(locId) );
			updateProfileListUI();
			return true;

		case R.id.menu_share:
			SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm");
			Object[] max_pollutant = GlobalData.getMaxPollutant(locId, GlobalData.getPref_AQIStandard(this));
			String airType = GlobalData.getPollutantDisplayName(max_pollutant[0].toString());
			Integer aqi_value = (Integer)max_pollutant[1];
			Float cc_value = (Float)max_pollutant[2]*1000;
			Date time = GlobalData.getAirdata(locId,"hour").getTime();
			String city = map.get("city");
			String locName = map.get("name");
			String msg = String.format( getString(R.string.share_message),
					getString(R.string.app_name),
					(( time == null ) ? "" : fmt.format(time)),
					city+locName,
					airType,
					cc_value,
					aqi_value,
					(( cc_value.compareTo(0f)==0 ) ? "" : getString(AQI.AQICategory(aqi_value))),
					(( cc_value.compareTo(0f)==0 ) ? "" : getString(AQI.AQIAdvisory(aqi_value))) );
			String shareSubject = getString(R.string.share_subject);
				
			Intent share = new Intent(Intent.ACTION_SEND);
			share.setType("text/plain");
			share.putExtra(Intent.EXTRA_TEXT, msg);
			share.putExtra( Intent.EXTRA_SUBJECT, shareSubject );
			startActivity(Intent.createChooser(share, shareSubject));

			return true;
			
		case R.id.menu_moveup:
		    if( groupPos > 0 )
		        java.util.Collections.swap(GlobalData.gProfileList, groupPos, groupPos - 1);
		    updateProfileListUI();
			return true;
			
		case R.id.menu_movedown:
		    if( groupPos + 1 < GlobalData.gProfileList.size() )
		    java.util.Collections.swap(GlobalData.gProfileList, groupPos, groupPos + 1);
		    updateProfileListUI();
			return true;
			
		case R.id.menu_history:
		    mSpinnerHistoryTime.setSelection(0);
			prepareHistoryView(locId, map.get("name"), "hour");
			mFlipper.setInAnimation(AnimationUtils.loadAnimation(
					AirreportProfileActivity.this, R.anim.slide_right_in));
			mFlipper.setOutAnimation(AnimationUtils.loadAnimation(
					AirreportProfileActivity.this, R.anim.slide_left_out));
			mFlipper.showNext();
			return true;

		default:
			return super.onContextItemSelected(item);
		}
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev){
		super.dispatchTouchEvent(ev);    
		return mGestureDetector.onTouchEvent(ev); 
	}
	
	private void prepareHistoryView(String locId, String locName, String timename) {
		Log.i("HistoryView of " + locId + "/" + locName);
		mHistoryLocId = locId;
		mHistoryLocName = locName;
		
		AirData airdata = GlobalData.getAirdata(locId, timename);
		if( Log.isLoggable(Log.DEBUG)) Log.d(GlobalData.gGson.toJson(airdata));
		if( airdata == null || airdata.getLength() == 0 || airdata.getTime() == null ) {
			Log.e("Location " + locId + " has invalid data");
			return;
		}

		mTextHistoryTitle.setText( locName );
		
		int field = GlobalData.gTimeTypeMap.get(timename);
	    int maxlength = GlobalData.gTimeLengthMap.get(timename);
		Calendar cal = Calendar.getInstance();
		cal.setTime(airdata.getTime());
		
		Calendar caltemp = (Calendar) cal.clone();
		List<String> marks = new ArrayList<String>();
		int length = Math.min(maxlength, airdata.getLength());
		for( int i = length-1; i >= 0; i--) {
		    String markstr = GlobalData.getDisplayName(this, caltemp, field);
		    if( field == Calendar.DAY_OF_MONTH ) {
		        markstr = Integer.toString(caltemp.get(Calendar.MONTH)+1)
		                + "." + markstr;
		    }
		    marks.add(0, markstr);
		    caltemp.add(field, -1);
		}

		mHistoryView.clearHistoryData();
		mHistoryView.setHorizonMark(marks);

		Set<String> pollutantList = GlobalData.getPref_SelectPollutants(this);
		String standard = GlobalData.getPref_AQIStandard(this);
		for(String pollutant : GlobalData.gItemNameList) {
		    if( ! pollutantList.contains(pollutant) ) continue;
		    
			List<Float> ccList = airdata.getData(pollutant); 
			if(  ccList == null ) continue;

			List<Float> aqiList = new ArrayList<Float>();
			for( Float cc : ccList ) {
				aqiList.add( (float)AQI.getAQI(pollutant, cc, standard) );
			}
			mHistoryView.addHistoryData(
					aqiList,
					(int)GlobalData.getPollutantDisplayColor(pollutant),
					GlobalData.getPollutantDisplayName(pollutant)
					);
		}
		mHistoryView.invalidate();
	}
	
	private boolean onFling( MotionEvent e1, MotionEvent e2 ) {
		if( mFlipper.getDisplayedChild() != 0 ) {
			// Possibly switch back, so retore it
			ignoreNextClick = false;
			return true;
		}
		
		int[] point = new int[2];
		mListProfile.getLocationOnScreen(point);
		Log.i("View: "+point[0]+","+point[1]);
		Log.i("Motion: "+(int)e1.getRawX()+","+(int)e1.getRawY());
		point[0] = (int)e1.getRawX() - point[0];
		point[1] = (int)e1.getRawY() - point[1];
		if( point[0]>0 || point[1]>0) {
			// Prevent the "tap up" expand any group
			ignoreNextClick = true;

			long packedPosition = mListProfile.getExpandableListPosition(mListProfile.pointToPosition(point[0], point[1]));
			int type  = ExpandableListView.getPackedPositionType(packedPosition);
			int group = ExpandableListView.getPackedPositionGroup(packedPosition);
			@SuppressWarnings("unused")
            int child = ExpandableListView.getPackedPositionChild(packedPosition);

			if( type == ExpandableListView.PACKED_POSITION_TYPE_GROUP ||
					type == ExpandableListView.PACKED_POSITION_TYPE_CHILD ) {
				if( group < mProfiles.size() - 1 ) {
				    mSpinnerHistoryTime.setSelection(0);
					HashMap<String, String> map = mProfiles.get(group);
					prepareHistoryView(map.get("id"), map.get("name"), "hour");
					return true;
				}
			}
		}
		
		Toast.makeText(
				this,
				getString(R.string.tip_flip),
				Toast.LENGTH_LONG).show();
		return false;
	}
	
	@Override
	protected void onStart() {
		super.onStart();

		// Only check update when last prompt time is up-to-date
		// Or it will be done in onCreate
		if( ! mRestoringPrefs )
		    checkUpdate();
	}
	
	@Override
	protected void onResume() {
		super.onResume();

		// update UI so pollutants selection changing would be applied
		if( ! mRestoringPrefs ) updateProfileListUI();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getSupportMenuInflater();
		inflater.inflate(R.menu.airreport, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		switch (item.getItemId()) {
		case R.id.menu_sharescreen:
		    shareScreen();
		    return true;
		case R.id.menu_refresh:
			updateAirdata(GlobalData.gProfileList.toArray(new String[]{}), new Runnable(){
				public void run() {
					updateProfileListUI();
				}
			});
			return true;
		case R.id.menu_usage:
			showUsage();
			return true;
		case R.id.menu_clearall:
			AlertDialog.Builder b = new AlertDialog.Builder(this).setTitle(getString(R.string.clearall));
			b.setMessage(getString(R.string.clearall_confirm));
			b.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener(){
				@Override
				public void onClick(DialogInterface dialog, int which) {
					GlobalData.clearAll();

                    // Since restoreLocationPrefs will be called seperately now
                    GlobalData.savePrefs(AirreportProfileActivity.this);

					updateProfileListUI();
				}
			});
			b.setNegativeButton(getString(android.R.string.cancel), null);
			b.show();
			return true;
		case R.id.menu_feedback:
			final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
			String title = String.format(getString(R.string.feedback_title), getString(R.string.app_name), GlobalData.getVersionName(this));
			emailIntent .setType("message/rfc822");
			//emailIntent .setType("plain/text"); 
			emailIntent .putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{"boodweb@gmail.com"});     
			emailIntent .putExtra(android.content.Intent.EXTRA_SUBJECT, title);     
			//emailIntent .putExtra(android.content.Intent.EXTRA_TEXT, yourBodyText);     
			startActivity(Intent.createChooser(emailIntent, "Send mail..."));
			return true;
		case R.id.menu_checkupdate:
			handler.mUserCommand = true;
			new Thread() {
				public void run() {
				    String path = (BuildConfig.DEBUG?GlobalData.gDebugUpdatePath:GlobalData.gUpdatePath);
					mUpdateRequest.doGet(path);
				}
			}.start();
			return true;
		case R.id.menu_setting:
		    Intent settingIndent = new Intent(this, SettingsActivity.class);
		    startActivity( settingIndent );
		    return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}
	
	private void showDialog( String title, String text ) {
		AlertDialog dialog = new AlertDialog.Builder(this).setTitle(title)
				.setMessage(text)
				.setPositiveButton(getString(R.string.close), null).show();
		TextView textView = (TextView) dialog.findViewById(android.R.id.message);
		textView.setTextSize(15);
	}
	
	private void showUsage() {
		String text = String.format(getString(R.string.usage_content), getString(R.string.app_name), GlobalData.getVersionName(this));
		String title = getString(R.string.usage);
		showDialog( title, text );
	}
	
	private void showWhatsNew() {
	    String text = String.format(getString(R.string.whats_new_text), getString(R.string.app_name), GlobalData.getVersionName(this));
		String title = getString(R.string.whats_new_title);
		showDialog( title, text );
	}
	
	private void newAirdataArrived(String strNewData)
	{
		Type type = new TypeToken<HashMap<String, HashMap<String,AirData>>>(){}.getType();
		HashMap<String, HashMap<String,AirData>> newDataMap = null;
		try {
		    newDataMap = mGson.fromJson(strNewData, type);
		} catch(Exception e) {
		    // Prevent the crash
		    Log.e("Bad data arrived: " + e.getMessage() );
		}
		if( newDataMap != null ) {
			for( String id : newDataMap.keySet() ) {
			    HashMap<String, AirData> newMap = newDataMap.get(id);
			    for( String time : GlobalData.gTimeTypeMap.keySet() ) {
			        AirData oldData = GlobalData.getAirdata(id, time);
			        AirData newData = newMap.get(time);
			        if( newData == null ) {
			            newData = new AirData();
			        }
			        if( oldData!=null && oldData.getTime()!=null ) {
			            oldData.merge(newData, time);
			        } else {
			            GlobalData.putAirdata(id, time, newData);
			        }
			    }
			}
		} else {
			Toast.makeText(
					this,
					getString(R.string.data_format_error),
					Toast.LENGTH_LONG).show();
		}
	}
	
	public void updateAirdata(final String [] idList, final Runnable run)
	{
		ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo ni = cm.getActiveNetworkInfo();
		if(  ni == null || !ni.isAvailable() || !ni.isConnected() ) { // Network unavailable
			Toast.makeText(
									this,
									getString(R.string.no_network),
									Toast.LENGTH_LONG).show();
			for( String id : idList) {
					for( String time : GlobalData.gTimeTypeMap.keySet() ) {
					    AirData oldData = GlobalData.getAirdata(id, time);
					    if( oldData == null ) GlobalData.putAirdata(id, time, new AirData());
					}
			}
			if ( run != null ) run.run();
			return;
		}
		
		if( idList ==null || idList.length == 0 ) {
		    if ( run != null ) run.run();
			return;
		}

		// Prepare the query json string
		final ArrayList<HashMap<String,String>> postData = new ArrayList();
		for( String id : idList) {
		    HashMap<String,String> map = new HashMap<String, String>();
		    Calendar now = Calendar.getInstance();
		    Calendar old = Calendar.getInstance();
		    Log.d("id="+id);
		    for( String time : GlobalData.gTimeTypeMap.keySet() ) {
		        AirData oldData = GlobalData.getAirdata(id, time);
		        Log.d("olddata (" + time + ")= " + GlobalData.gGson.toJson(oldData));
		        
		        int field = GlobalData.gTimeTypeMap.get(time);
		        int maxLength = GlobalData.gTimeLengthMap.get(time);
		        if( oldData != null && oldData.getTime() != null )
		            old.setTime(oldData.getTime());
		        else {
		            map.put(time, Integer.toString(maxLength));
		            continue;
		        }
		        
		        int distance = AirData.getCalendarDistance(old, now, field);
		        if( distance > 0 ) {
		            if( field == Calendar.HOUR_OF_DAY && distance == 1 && now.get(Calendar.MINUTE) < 30 )
		                map.put(time, "0");
		            else
		                map.put(time, Integer.toString(distance));
		        } else {
		            map.put(time, "0");
		        }
		    }
		    map.put("id", id);
		    
		    boolean nonZero = false;
		    for( String time : GlobalData.gTimeTypeMap.keySet() ) {
		        String length = map.get(time);
		        if( !length.equals("0") )
		            nonZero = true;
		    }
		    
		    if( nonZero )
		        postData.add(map);
		}
		
		if( postData.size() == 0 ) {
			Toast.makeText(this,
					getString( R.string.refresh_toosoon),
					Toast.LENGTH_LONG).show();
			if ( run != null ) run.run();
			return;
		}

		// Use thread to prevent any possible UI freezes
		Thread thd = new Thread() {
			@Override
			public void run() {
			    String str = mGson.toJson(postData);
			    Log.d("postData=" + str );
				mGAERequest.doPost("/airdata", str);
				
				// This Runnable must be posted after messages are handled
				mGAEHandler.post(new Runnable() {
					@Override
					public void run() {
						if ( run != null ) run.run();
					}
				});
			}
		};
		thd.start();
	}
	
	private void checkUpdate() {
	    Calendar now = Calendar.getInstance();
	    Calendar lastUpdate = Calendar.getInstance();
	    lastUpdate.setTimeInMillis(GlobalData.gLastUpdateMessageTime);
	    FastDateFormat fmt = FastDateFormat.getInstance("yyyy-MM-dd mm:ss", Locale.US);
	    Log.i( String.format( "Last prompt time: %s, now: %s", fmt.format(lastUpdate), fmt.format(now) ) );
	    if ( ! DateUtils.isSameDay(now, lastUpdate) ) {
	        new Thread() {
	            public void run() {
	                String path = (BuildConfig.DEBUG?GlobalData.gDebugUpdatePath:GlobalData.gUpdatePath);
	                mUpdateRequest.doGet(path);
	            }
	        }.start();
	    }
	}
	
	private void shareScreen() {
	    View content = findViewById(R.id.flipper);
	    content.buildDrawingCache();
	    Bitmap bitmap = content.getDrawingCache().copy(Bitmap.Config.RGB_565, false); // No alpha channel (no transparency)
	    content.destroyDrawingCache();
	    File cacheDir = this.getExternalCacheDir();
	    File file = new File(cacheDir, "airreport.png");;
	    try 
	    {
	        FileOutputStream ostream = new FileOutputStream(file);
	        bitmap.compress(CompressFormat.PNG, 100, ostream);
	        ostream.close();
	    } 
	    catch (Exception e) 
	    {
	        Log.e(e.toString());
	        Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
	        return;
	    }
	    
	    Intent shareIntent = new Intent(Intent.ACTION_SEND);
	    shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
	    shareIntent.setType("image/*");

	    // For a file in shared storage.  For data in private storage, use a ContentProvider.
	    Uri uri = Uri.fromFile(file);
	    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
	    Log.i(file.getAbsolutePath());
	    
	    String shareSubject = getString(R.string.share_subject);
	    String shareText = String.format( getString(R.string.share_text), getString(R.string.app_name) );
	    shareIntent.putExtra( Intent.EXTRA_SUBJECT, shareSubject );
	    shareIntent.putExtra(Intent.EXTRA_TEXT, shareText );
	    startActivity(Intent.createChooser(shareIntent, shareSubject));
	}

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos,
            long id) {
        String timename = "hour";
        switch( pos ) {
        case GlobalData.HISTORY_TIME_INDEX_HOUR:
            timename = "hour";
            break;
        case GlobalData.HISTORY_TIME_INDEX_DAY:
            timename = "day";
            break;
        case GlobalData.HISTORY_TIME_INDEX_MONTH:
            timename = "month";
            break;
        }
        prepareHistoryView(mHistoryLocId, mHistoryLocName, timename);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        
    }
}
